# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# (C) 2012 Remek Zajac
#!/usr/bin/python

import gtk
import cairo
import math
import os
import Tkinter #for clipboard

#local modules
import pyGpxOSMTiles
import pyGeometry
import pyGPX
import pyGpxCairoDrawToolkit
import pyOSM2SQL
import pyRouting
import pyOSMTileRenderer
import pyScripting


class GPXMapWidget(gtk.DrawingArea):
    #calibrators
    MaxZoom = 18
    SemiPanOnZoom = 0.6
    TARGET_TYPE_URI_LIST = 80
    GTK_BUTTON_LEFT = 1
    GTK_BOTTON_MIDDLE = 2    
    GTK_BUTTON_RIGHT = 3

    KStaticDrawableComponents = {}
    
    def __init__(self, a_config, latlon=(51.110115,17.031952), zoom=12):
        super(GPXMapWidget, self).__init__()
        self.m_config = a_config
        self.lastButtonPressedAt = (-1,-1)
        self.localTileRenderer = None
        self.osmReferenceMap = pyGpxOSMTiles.OSMTilesReferenceMap(self, latlon, zoom)
        self.set_size_request(400, 300)
        self.statusBar = None
        self.m_drawingToolkit = pyGpxCairoDrawToolkit.GeoDrawingToolkit()
        self.m_viewMenu = gtk.Menu()
        self.m_toolsMenu = gtk.Menu()

        #register for events
        self.connect("expose-event", self.expose)
        self.connect("motion_notify_event", self.motion_notify_event)
        self.connect("button_press_event", self.button_press_event)
        self.connect("button_release_event", self.button_release_event) 
        self.connect("scroll-event", self.scroll_event) 
        self.connect("configure-event", self.configure_event) 
        self.drag_dest_set( gtk.DEST_DEFAULT_MOTION |
                  gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP,
                  [ ( 'text/uri-list', 0, GPXMapWidget.TARGET_TYPE_URI_LIST ) ], gtk.gdk.ACTION_COPY)
        self.connect("drag-data-received", self.drag_data_received_event)

        self.set_events(gtk.gdk.EXPOSURE_MASK 
            | gtk.gdk.BUTTON_PRESS_MASK
            | gtk.gdk.BUTTON_RELEASE_MASK
            | gtk.gdk.POINTER_MOTION_MASK
            | gtk.gdk.POINTER_MOTION_HINT_MASK
            | gtk.gdk.SCROLL_MASK)


        self.staticDrawableComponents = {}
        #A bit inapproprietary, the drawing sequence depends on the 'alphabetical' sorting order of
        #the drawable components and since the opaque OSM tiles need to go first, they need to
        #be first in the sorting order. Hence the trailing space       
        self.staticDrawableComponents[" Downloaded OSM Tiles"] = pyGpxOSMTiles.OSMTilesMap(self.osmReferenceMap, pyGpxOSMTiles.OSMTiles())
        self.staticDrawableComponents["Local OSM Tiles"] = pyGpxOSMTiles.OSMTilesMap(self.osmReferenceMap, pyOSMTileRenderer.OSMLocalTileRenderingEngine(self))
        self.staticDrawableComponents["Node RAM Cache"] = pyOSM2SQL.WayNodesRAMCacheRenderer(self.osmReferenceMap)
        self.drawableComponents = {}
        self.drawableComponents[" Downloaded OSM Tiles"] = self.staticDrawableComponents[" Downloaded OSM Tiles"]
        
    def latlon2pix(self, latlon):
        return self.osmReferenceMap.latlon2pix(latlon)

    def pix2latlon(self, pixxy):
        return self.osmReferenceMap.pix2latlon(pixxy)

    def invalidate(self, calledBy): 
        self.viewMenu()
        self.toolsMenu()        
        self.osmReferenceMap.invalidate(self)
        for k,v in self.drawableComponents.iteritems():
            v.invalidate(self)
        self.queue_draw()
           
    def region(self):
        NW = self.pix2latlon((0,0))
        SE = self.pix2latlon((self.allocation.width, self.allocation.height))
        return pyGeometry.GeoRegion((NW[0],NW[1],SE[0],SE[1]))

    def expose(self, widget, event):
        cr = widget.window.cairo_create()
        for k,v in sorted(self.drawableComponents.iteritems()):
            v.draw(cr)

    def motion_notify_event(self, widget, event):
        if self.lastButtonPressedAt != (-1,-1):
            #pan the map
            lanlonVector = pyGeometry.Vector((self.pix2latlon((event.x, event.y)),self.pix2latlon(self.lastButtonPressedAt)))
            newLatLonCentre = lanlonVector.addToPoint(self.osmReferenceMap.latlonCentre)
            self.osmReferenceMap.panAndZoom(newLatLonCentre)
            self.invalidate(self)
            self.lastButtonPressedAt = (event.x, event.y)
        if self.statusBar:
            latlon = self.pix2latlon((event.x, event.y))
            self.statusBar.push(1, "lat:"+str(latlon[0])+" lon:"+str(latlon[1])+' zoom:'+str(self.osmReferenceMap.zoom))

    def scroll_event(self, widget, event):
        newLatLonCentre = self.osmReferenceMap.latlonCentre
        newZoom = self.osmReferenceMap.zoom
        if event.direction == gtk.gdk.SCROLL_UP and newZoom < GPXMapWidget.MaxZoom:
            #lets zoom with the centre moved (by GPXMapWidget.SemiPanOnZoom) towards the current mouse position
            #the reason why wy don't move exactly to the current mouse position is that it feels unnatural,
            #but please judge yourself if you find it funny
            latlonEvent = self.pix2latlon((event.x, event.y))
            latlonSemiPanVector = pyGeometry.Vector((newLatLonCentre,latlonEvent))
            latlonSemiPanVector = latlonSemiPanVector.setLength(latlonSemiPanVector.length()*GPXMapWidget.SemiPanOnZoom)
            newLatLonCentre = latlonSemiPanVector.addToPoint(newLatLonCentre)
            newZoom += 1
        elif event.direction == gtk.gdk.SCROLL_DOWN and newZoom > 1:
            newZoom -= 1
        self.osmReferenceMap.panAndZoom(newLatLonCentre, newZoom)                
        self.invalidate(self)
    
    def drag_data_received_event(self, widget, context, x, y, selection, targetType, time):
        print "dropped:", selection.data
        uri = selection.data.strip('\r\n\x00')
        if uri.find('.gpx') != -1:
            uri = uri.strip('file://') #GPX renderer is offended by 'file://'                
            uri = uri.replace('%20',' ') #GPX renderer is offended by '%20'
            gpxRenderer = pyGPX.GPXRenderer(self.osmReferenceMap, uri)
            gpxRenderer.parse()
            self.drawableComponents[uri] = gpxRenderer        
            self.osmReferenceMap.showRegion(gpxRenderer.region())
        elif uri.find('.osm') != -1:
            db = pyOSM2SQL.OSMGisDatabase()
            db.parseOsm(uri, self.region())
        elif uri.find('.script') != -1:
            uri = uri.strip('file://') #GPX renderer is offended by 'file://'                
            uri = uri.replace('%20',' ') #GPX renderer is offended by '%20'            
            scriptEngine = pyScripting.ScriptingEngine(self, uri)
            scriptEngine.do(self.invalidate)
        else:
            print "Unsupported file type dropped, ignoring"

    def righclick_contextmenu(self, event):
        menu = gtk.Menu()
        enableRoutingItems = True
        if self.drawableComponents.has_key("Route"):
            enableRoutingItems = not self.drawableComponents["Route"].isAlive()
        fromMenuItem = gtk.MenuItem("Ride From")
        menu.append(fromMenuItem)
        fromMenuItem.set_sensitive(enableRoutingItems)
        fromMenuItem.connect("activate", self.on_from_menu_item, (event.x, event.y))        
        if self.drawableComponents.has_key("Route"): 
            toMenuItem = gtk.MenuItem("Ride To")
            menu.append(toMenuItem)
            toMenuItem.set_sensitive(enableRoutingItems)
            toMenuItem.connect("activate", self.on_to_menu_item, (event.x, event.y))
        mouseLocation = gtk.MenuItem("Copy mouse location")
        mouseLocation.connect("activate", self.on_mouse_location_menu_item, (event.x, event.y)) 
        menu.append(mouseLocation)
        
        menu.show_all()
        menu.popup(None, None, None, event.button, event.get_time())

    def on_to_menu_item(self, widget, mouseevent):       
        assert(self.drawableComponents.has_key("Route"))        
        self.drawableComponents["Route"].setDestination(self.pix2latlon((mouseevent[0], mouseevent[1])))
        self.drawableComponents["Route"].do(self.invalidate)
            
    def on_from_menu_item(self, widget, mouseevent):
        if self.drawableComponents.has_key("Route"):
            self.drawableComponents["Route"].setOrigin(self.pix2latlon((mouseevent[0], mouseevent[1])))
            self.drawableComponents["Route"].do(self.invalidate)
        else:
            self.drawableComponents["Route"] = pyRouting.Route(self.osmReferenceMap, self.pix2latlon((mouseevent[0], mouseevent[1])))

    def route(self, fromlatlon, tolatlon):
        self.drawableComponents["Route"] = pyRouting.Route(self.osmReferenceMap, fromlatlon, tolatlon)
        return self.drawableComponents["Route"]

    def on_mouse_location_menu_item(self, widget, mouseevent):
        r = Tkinter.Tk()
        r.withdraw()
        r.clipboard_clear()
        location = self.pix2latlon((mouseevent[0], mouseevent[1]))
        r.clipboard_append(str(location[0])+','+str(location[1]))
        r.destroy()
       
    def button_press_event(self, widget, event):
        if event.button == GPXMapWidget.GTK_BUTTON_LEFT:
            self.lastButtonPressedAt = (event.x, event.y)
        elif event.button == GPXMapWidget.GTK_BUTTON_RIGHT:
            self.righclick_contextmenu(event)
        else:
            print "Middle button clicked - no function"

    def button_release_event(self, widget, event):
        if event.button == GPXMapWidget.GTK_BUTTON_LEFT:
            self.lastButtonPressedAt = (-1,-1)        

    def configure_event(self, widget, event):
        self.invalidate(self)

    def drawingToolkit(self):
        return self.m_drawingToolkit

    def viewMenu(self):
        #empty the menu
        for i in self.m_viewMenu.get_children():
            self.m_viewMenu.remove(i)

        #compose the list (keep it in the same order always)
        menuItemsList = []

        for drawableComponentKey in self.staticDrawableComponents:
            if not self.drawableComponents.has_key(drawableComponentKey):
                menuItemsList.append((drawableComponentKey, False))
            else:
                menuItemsList.append((drawableComponentKey, True))
        
        for drawableComponentKey in self.drawableComponents:
            if not self.staticDrawableComponents.has_key(drawableComponentKey):
                menuItemsList.append((drawableComponentKey, True))

        #finally compose the menu
        for element in menuItemsList:
            menuItem = gtk.CheckMenuItem(element[0])
            menuItem.set_active(element[1])
            menuItem.show()
            menuItem.connect("activate", self.on_view_menu_item_activate, element[0])
            self.m_viewMenu.append(menuItem)            
        return self.m_viewMenu

    def toolsMenu(self):
        #empty the menu
        for i in self.m_toolsMenu.get_children():
            self.m_toolsMenu.remove(i)

        menuItem = gtk.MenuItem("Save Route")
        if self.drawableComponents.has_key("Route") and not self.drawableComponents["Route"].isAlive():
            menuItem.set_sensitive(True)
        else:
            menuItem.set_sensitive(False)
        menuItem.show()
        menuItem.connect("activate", self.on_save_route)
        self.m_toolsMenu.append(menuItem)             
                 
        return self.m_toolsMenu        

    def on_view_menu_item_activate(self, event, key):
        if self.drawableComponents.has_key(key):
            del self.drawableComponents[key]
        else:
            assert(self.staticDrawableComponents.has_key(key))
            self.drawableComponents[key] = self.staticDrawableComponents[key]
        self.invalidate(self)

    def on_save_route(self, event):
        assert(self.drawableComponents.has_key("Route") and not self.drawableComponents["Route"].isAlive())
        gpxWriter = pyGPX.WriteGpx()
        fileName = 'route.gpx'
        gpxWriter.do(self.drawableComponents["Route"], fileName)
        print "Route saved to file:", fileName

    def setStatusBar(self, statusBar):
        self.statusBar = statusBar

    def config(self):
        return self.m_config

  
        
